package com.heima.wemedia.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.heima.apis.article.IArticleClient;
import com.heima.common.constants.WemediaConstants;
import com.heima.common.constants.WmNewsMsgConstants;
import com.heima.common.exception.BusinessException;
import com.heima.common.exception.CustomException;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.wemedia.dtos.NewsAuthDto;
import com.heima.model.wemedia.dtos.WmNewsDto;
import com.heima.model.wemedia.dtos.WmNewsDto2;
import com.heima.model.wemedia.dtos.WmNewsPageReqDto;
import com.heima.model.wemedia.pojos.WmMaterial;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.model.wemedia.pojos.WmNewsMaterial;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.model.wemedia.vos.WmNewsVo;
import com.heima.utils.thread.WmThreadLocalUtil;
import com.heima.wemedia.mapper.WmMaterialMapper;
import com.heima.wemedia.mapper.WmNewsMapper;
import com.heima.wemedia.mapper.WmNewsMaterialMapper;
import com.heima.wemedia.service.WmNewsAutoScanService;
import com.heima.wemedia.service.WmNewsService;
import com.heima.wemedia.service.WmNewsTaskService;
import com.heima.wemedia.utils.common.ServiceUtils;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.tess4j.TesseractException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class WmNewsServiceImpl extends ServiceImpl<WmNewsMapper, WmNews> implements WmNewsService {
    @Autowired
    private WmMaterialMapper wmMaterialMapper;
    @Autowired
    private WmNewsMaterialMapper wmNewsMaterialMapper;
    @Autowired
    private WmNewsAutoScanService wmNewsAutoScanService;
    @Autowired
    private IArticleClient articleClient;
    @Autowired
    private WmNewsTaskService wmNewsTaskService;
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    private static final String TYPE = "type";
    private static final String IMAGE = "image";
    private static final String VALUE = "value";
    private static final int PUBLISHED = 9;


    @Override
    public ResponseResult<List<WmNews>> findAll(WmNewsPageReqDto dto) {
        //  1. 检查参数
        if (dto == null) return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);

        //  1.2 检查分页参数
        dto.checkParam();

        //  1.3 获取当前登录用户信息
        WmUser currentUserInfo = Optional.ofNullable(WmThreadLocalUtil.getUser())
                .orElseThrow(() -> new BusinessException("用户未登录"));


        //  2 设置分页条件
        LambdaQueryWrapper<WmNews> lambdaQueryWrapper = new LambdaQueryWrapper<>();

        //  2.1 状态精确查询
        Short status = dto.getStatus();
        if (status != null) lambdaQueryWrapper.eq(WmNews::getStatus, status);

        //  2.2 频道精确查询
        Integer channelId = dto.getChannelId();
        if (channelId != null) lambdaQueryWrapper.eq(WmNews::getChannelId, channelId);

        //  2.3 时间范围查询
        Date beginPubDate = dto.getBeginPubDate();
        Date endPubDate = dto.getEndPubDate();
        if (beginPubDate != null && endPubDate != null) {
            if (beginPubDate.after(endPubDate))
                throw new BusinessException("开始时间不能晚于结束时间");
            lambdaQueryWrapper.between(WmNews::getCreatedTime, beginPubDate, endPubDate);
        }

        //  2.4 关键字模糊查询
        String keyword = dto.getKeyword();
        if (keyword != null && !keyword.trim().isEmpty())
            lambdaQueryWrapper.like(WmNews::getTitle, keyword);

        //  2.5 查询当前登录用户的文章
        lambdaQueryWrapper.eq(WmNews::getUserId, currentUserInfo.getId());

        //  2.6 发布时间倒序查询
        lambdaQueryWrapper.orderByDesc(WmNews::getCreatedTime);

        //  2. 分页查询
        Page<WmNews> page = new Page<>(dto.getPage(), dto.getSize());
        Page<WmNews> pageR = page(page, lambdaQueryWrapper);

        //  3. 结果返回
        return ServiceUtils.buildPageResponse(
                dto.getPage(),
                dto.getSize(),
                (int) pageR.getTotal(),
                pageR.getRecords());
    }

    /**
     * 发布修改文章或保存为草稿
     *
     * @param dto 文章信息
     */
    @Override
    public ResponseResult<AppHttpCodeEnum> submitOrSaveDraftNews(WmNewsDto dto)
            throws IOException, TesseractException {
        // 0.判断文章信息是否为空
        if (dto == null || dto.getContent() == null)
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);

        // 1.保存或修改文章
        WmNews wmNews = new WmNews();
        // 属性拷贝 属性名词和类型相同才能拷贝
        BeanUtils.copyProperties(dto, wmNews);
        // 封面图片  list --> string
        if (dto.getImages() != null && !dto.getImages().isEmpty()) {
            // [1.jpg, 2.jpg] --> 1.jpg, 2.jpg
            String imageStr = StringUtils.join(dto.getImages(), ",");
            wmNews.setImages(imageStr);
        }
        // 如果当前封面类型为自动 -1
        if (dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO))
            wmNews.setType(null);

        saveOrUpdateWmNews(wmNews);

        // 2.判断是否为草稿  如果为草稿结束当前方法
        if (dto.getStatus().equals(WmNews.Status.NORMAL.getCode()))
            return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);

        // 3.不是草稿，保存文章内容图片与素材的关系
        // 获取到文章内容中的图片信息
        List<String> materials = extractUrlInfo(dto.getContent());
        saveRelativeInfoForContent(materials, wmNews.getId());

        // 4.不是草稿，保存文章封面图片与素材的关系，如果当前布局是自动，需要匹配封面图片
        saveRelativeInfoForCover(dto, wmNews, materials);

        // 异步审核文章 -- 这里就不需要审核了, 添加定时任务后
        // wmNewsAutoScanService.autoScanWmNewsAsync(wmNews.getId());

        // 添加定时任务
        wmNewsTaskService.addNewsToTask(wmNews.getId(), wmNews.getPublishTime());

        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);

    }

    /**
     * 通过 id 查询文章
     *
     * @param articleID 文章id
     */
    @Override
    public ResponseResult<WmNews> queryById(Integer articleID) {
        if (articleID == null || articleID <= 0) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        WmNews info = getById(articleID);

        if (info == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "文章不存在");
        }

        return ResponseResult.okResult(info);
    }

    /**
     * 删除文章
     *
     * @param id 文章id
     */
    @Override
    public ResponseResult<String> deleteById(Integer id) {
        // 1. 参数校验
        validateArticleId(id);

        // 2. 查询文章是否存在并检查状态
        WmNews news = lambdaQuery().eq(WmNews::getId, id).one();
        if (news == null) {
            log.warn("尝试删除不存在的文章, ID: {}", id);
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "文章不存在");
        }

        if (news.getStatus() == PUBLISHED) {
            log.warn("尝试删除已发布的文章, ID: {}", id);
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "文章已发布，不可删除");
        }

        // 3. 删除关联信息（如果有）
        int deletedCount = cleanRelatedMaterials(id);
        log.info("删除文章ID={}的关联材料，共{}条", id, deletedCount);

        // 4. 删除文章前, 先删除 article 表的数据以及minio存储的静态资源 -> xxxx.html
        Long articleId = news.getArticleId();
        articleClient.delArticleById(articleId);

        // 5. 删除文章
        boolean isDeleted = lambdaUpdate()
                .eq(WmNews::getId, id)
                .remove();

        if (isDeleted) {
            log.info("成功删除文章, ID: {}", id);
            return ResponseResult.okResult("删除文章成功");
        }

        log.error("删除文章失败, ID: {}", id);
        return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR, "删除文章失败");
    }

    /**
     * 上架/下架文章  -- 若文章不是处于发布状态, 不能操作
     *
     * @param dto 文章信息
     */
    @Override
    public ResponseResult<String> downOrUp(WmNewsDto2 dto) {
        // 1. 参数校验
        ResponseResult<String> stringResponseResult = validateDownOrUpParams(dto);
        if (stringResponseResult != null) return stringResponseResult;

        // 2. 获取文章
        WmNews one = lambdaQuery().eq(WmNews::getId, dto.getId()).one();
        if (one == null) {
            log.warn("尝试操作不存在的文章, ID: {}", dto.getId());
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "文章不存在");
        }

        // 3. 判断文章状态
        if (!(one.getStatus() == PUBLISHED)) {
            log.warn("尝试操作未发布的文章, ID: {}, 当前状态: {}", dto.getId(), one.getStatus());
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "只有已发布的文章才能进行上下架操作");
        }

        // 4. 更新文章状态
        return updateArticleStatus(dto.getId(), dto.getEnable(), one.getArticleId());
    }

    /**
     * 获取文章列表
     *
     * @param dto 查询参数
     */
    @Override
    public ResponseResult<List<WmNewsVo>> listVo(NewsAuthDto dto) {
        // 1. 参数校验
        if (dto == null) {
            log.error("参数不合法: DTO对象为空");
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "请求参数不能为空");
        }
        dto.checkParam();

        // 获取当前登录用户信息
        WmUser user = WmThreadLocalUtil.getUser();
        if (user == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
        }

        // 2. 构建查询条件
        LambdaQueryWrapper<WmNews> queryWrapper = new LambdaQueryWrapper<>();
        // 2.1 标题模糊查询
        if (StringUtils.isBlank(dto.getTitle())) {
            queryWrapper.like(WmNews::getTitle, dto.getTitle());
        }
        // 2.2 审核状态精确查询
        if (dto.getStatus() != null) {
            queryWrapper.eq(WmNews::getStatus, dto.getStatus());
        }
        // 2.3 创建时间倒序排序
        queryWrapper.orderByDesc(WmNews::getCreatedTime);

        // 3. 构建分页条件
        Integer currentPage = dto.getPage();
        Integer pageSize = dto.getSize();
        currentPage = ((currentPage - 1) * pageSize); // 计算当前页的起始索引
        currentPage = Math.max(currentPage, 1); // 确保当前页索引大于等于1

        Page<WmNews> page = new Page<>(currentPage, pageSize);

        // 4. 按条件分页查询
        page = page(page, queryWrapper);

        if (page.getTotal() == 0) {
            return ResponseResult.okResult(Collections.singletonList(null));
        }

        List<WmNews> records = page.getRecords();

        // 5. 封装返回结果
        List<WmNewsVo> wmNewsVos = BeanUtil.copyToList(records, WmNewsVo.class);
        System.out.println(wmNewsVos);
        return ResponseResult.okResult(wmNewsVos);
    }

    /**
     * 查询文章详情
     *
     * @param id 文章id
     */
    @Override
    public ResponseResult<WmNewsVo> findWmNewsVo(Integer id) {
        // 1. 参数校验
        if (id == null || id <= 0) {
            log.error("参数不合法: 文章ID为空");
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "文章ID不能为空");
        }

        // 2. 查询文章信息
        WmNews wmNews = lambdaQuery().eq(WmNews::getId, id).one();
        if (wmNews == null) {
            log.warn("当前查询的文章不存在, ID: {}", id);
        }

        // 3. 封装返回结果
        WmNewsVo wmNewsVo = BeanUtil.copyProperties(wmNews, WmNewsVo.class);
        return ResponseResult.okResult(wmNewsVo);
    }

    /**
     * 文章审核失败
     *
     * @param dto 审核失败信息
     */
    @Override
    public ResponseResult<AppHttpCodeEnum> authWmNews(NewsAuthDto dto, Short status) {
        // 1. 参数校验
        if (dto == null || dto.getId() == null || StringUtils.isBlank(dto.getMsg())) {
            log.error("参数不合法: DTO对象为空");
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "请求参数不能为空");
        }

        // 2. 判断文章是否存在
        WmNews wmNews = lambdaQuery().eq(WmNews::getId, dto.getId()).one();
        if (wmNews == null) {
            log.warn("当前查询的文章不存在, ID: {}", dto.getId());
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "文章不存在");
        }

        // 3. 判断文章是否处于待审核状态 -- 1
        if (wmNews.getStatus() != WmNews.Status.SUBMIT.getCode()) {
            log.warn("当前文章不是待审核状态, ID: {}, 当前状态: {}", dto.getId(), wmNews.getStatus());
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "当前文章不是待审核状态");
        }

        // 4. 设置文章审核 拒绝/通过原因、状态  2--fail, 4--pass
        wmNews.setStatus(status);
        wmNews.setReason(dto.getMsg());

        // 4. 修改文章状态
        boolean isUpdate = updateById(wmNews);

        // 5. 审核成功，则需要创建app端文章数据，并修改自媒体文章
        if (status.equals(WemediaConstants.WM_NEWS_AUTH_PASS)) {
            // 创建app端文章数据
            ResponseResult<Long> responseResult = wmNewsAutoScanService.saveAppArticle(wmNews);
            if (responseResult.getCode().equals(200)) {
                wmNews.setArticleId(responseResult.getData());
                wmNews.setStatus(WmNews.Status.PUBLISHED.getCode());
                updateById(wmNews);
            }
        }

        return isUpdate ? ResponseResult.okResult(AppHttpCodeEnum.SUCCESS)
                : ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR);
    }

    // 参数校验
    private ResponseResult<String> validateDownOrUpParams(WmNewsDto2 dto) {
        if (dto == null) {
            log.error("参数不合法: DTO对象为空");
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "请求参数不能为空");
        }

        if (dto.getId() == null) {
            log.error("参数不合法: 文章ID为空");
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "文章ID不能为空");
        }

        if (dto.getEnable() == null) {
            log.error("参数不合法: 上下架状态为空");
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "上下架状态不能为空");
        }
        return null;
    }

    private ResponseResult<String> updateArticleStatus(Integer id, Short enableStatus, Long articleId) {
        String action = enableStatus == 1 ? "上架" : "下架";

        // 更新news表的上下架状态 (enable字段)
        boolean isSuccess = lambdaUpdate()
                .eq(WmNews::getId, id)
                .set(WmNews::getEnable, enableStatus)
                .update();

        // 发送消息，通知article端修改文章上下架状态 (is_down字段)
        if (articleId != null && articleId > 0) {
            Map<String, Object> map = new HashMap<>();
            map.put(WmNewsMsgConstants.WM_NEWS_UP_OR_DOWN_ARTICLE_ID, articleId);
            map.put(WmNewsMsgConstants.WM_NEWS_UP_OR_DOWN_STATUS, enableStatus);
            kafkaTemplate.send(WmNewsMsgConstants.WM_NEWS_UP_OR_DOWN_TOPIC,
                    JSON.toJSONString(map));
        }

        if (isSuccess) {
            log.info("wmNews端 文章{}成功, ID: {}, 新状态: {}", action, id, enableStatus);
            return ResponseResult.okResult(String.format("文章%s成功", action));
        }

        log.error("文章{}失败, ID: {}", action, id);
        return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR,
                String.format("wmNews端 文章%s失败，请稍后重试", action));
    }

    // 参数校验
    private void validateArticleId(Integer id) {
        if (id == null || id <= 0) {
            log.error("文章ID不合法: {}", id);
            throw new BusinessException(AppHttpCodeEnum.PARAM_INVALID,
                    id == null ? "文章ID不可缺省" : "文章ID必须大于0");
        }
    }

    // 关联数据清理
    private int cleanRelatedMaterials(Integer articleId) {
        List<WmNewsMaterial> materials = wmNewsMaterialMapper.selectList(
                Wrappers.<WmNewsMaterial>lambdaQuery()
                        .eq(WmNewsMaterial::getNewsId, articleId)
                        .select(WmNewsMaterial::getId));

        if (!materials.isEmpty()) {
            List<Integer> ids = materials.stream()
                    .map(WmNewsMaterial::getId)
                    .collect(Collectors.toList());
            return wmNewsMaterialMapper.deleteBatchIds(ids);
        }
        return 0;
    }

    /**
     * 设置封面类型并保存封面图片与素材的关系
     *
     * <p>1. 封面类型自动设置规则：
     * <ul>
     *   <li>无图片（0张） → 无图（type 0）</li>
     *   <li>1-2张图片 → 单图（type 1）</li>
     *   <li>3张及以上图片 → 多图（type 3）</li>
     * </ul>
     *
     * <p>2. 同时保存封面图片与素材的关联关系
     *
     * @param dto       文章内容数据传输对象
     * @param wmNews    文章实体对象
     * @param materials 文章内容中提取的图片URL列表
     */
    private void saveRelativeInfoForCover(WmNewsDto dto, WmNews wmNews, List<String> materials) {

        // 如果当前封面类型为自动，则设置封面类型的数据
        if (!WemediaConstants.WM_NEWS_TYPE_AUTO.equals(dto.getType())) { // 避免NPE
            if (CollectionUtils.isNotEmpty(dto.getImages())) {
                saveRelativeInfo(dto.getImages(), wmNews.getId(), WemediaConstants.WM_COVER_REFERENCE);
                return;
            }
        }
        List<String> coverImages = null;
        final int size = materials.size();
        // 多图 -- 截取三张图展示
        if (size >= 3) {
            wmNews.setType(WemediaConstants.WM_NEWS_MANY_IMAGE);
            coverImages = materials.stream().limit(3).collect(Collectors.toList());
        } else if (size >= 1) {
            // 单图
            wmNews.setType(WemediaConstants.WM_NEWS_SINGLE_IMAGE);
            coverImages = materials.stream().limit(1).collect(Collectors.toList());
        } else {
            // 无图
            wmNews.setType(WemediaConstants.WM_NEWS_NONE_IMAGE);
        }

        // 修改文章
        if (CollectionUtils.isNotEmpty(coverImages))
            wmNews.setImages(StringUtils.join(coverImages, ","));

        updateById(wmNews);
    }


    /**
     * 处理文章内容图片与素材的关系
     *
     * @param materials 图片的url集合
     * @param newsId    文章id
     */
    private void saveRelativeInfoForContent(List<String> materials, Integer newsId) {
        saveRelativeInfo(materials, newsId, WemediaConstants.WM_CONTENT_REFERENCE);
    }


    /**
     * 保存文章图片与素材的关系到数据库中
     *
     * @param materials 图片的url集合
     * @param newsId    文章id
     * @param type      图片与素材关系
     */
    private void saveRelativeInfo(List<String> materials, Integer newsId, Short type) {
        if (materials == null || materials.isEmpty()) return;

        // 通过图片的url查询素材的id
        List<WmMaterial> dbMaterials = wmMaterialMapper.selectList(
                Wrappers.<WmMaterial>lambdaQuery()
                        .in(WmMaterial::getUrl, materials));

        // 判断素材是否有效
        if (dbMaterials == null || dbMaterials.isEmpty()) {
            // 手动抛出异常   第一个功能：能够提示调用者素材失效了，第二个功能，进行数据的回滚
            throw new CustomException(AppHttpCodeEnum.MATERIALS_REFERENCE_FAIL);
        }

        if (materials.size() != dbMaterials.size()) {
            throw new CustomException(AppHttpCodeEnum.MATERIALS_REFERENCE_FAIL);
        }

        List<Integer> idList = dbMaterials.stream()
                .map(WmMaterial::getId)
                .collect(Collectors.toList());

        // 批量保存
        wmNewsMaterialMapper.saveRelations(idList, newsId, type);
    }


    /**
     * 提取文章内容中的图片信息
     *
     * @param content 文章内容
     */
    private List<String> extractUrlInfo(String content) throws JsonProcessingException {
        // 使用 jackson 库 解析json字符串为 map
        ObjectMapper mapper = new ObjectMapper();
        List<Map<String, Object>> maps = mapper.readValue(content,
                new TypeReference<List<Map<String, Object>>>() {
                });

        return maps.stream()
                .filter(map -> IMAGE.equals(map.get(TYPE)))
                .map(map -> (String) map.get(VALUE))
                .collect(Collectors.toList());
    }

    /**
     * 保存或修改文章
     *
     * @param wmNews 文章对象
     */
    private void saveOrUpdateWmNews(WmNews wmNews) {
        // 补全属性
        wmNews.setUserId(WmThreadLocalUtil.getUser().getId());
        wmNews.setCreatedTime(new Date());
        wmNews.setSubmittedTime(new Date());
        wmNews.setEnable((short) 1); // 默认上架

        if (wmNews.getId() == null) save(wmNews); // 保存
        else {
            // 修改
            // 删除文章图片与素材的关系
            wmNewsMaterialMapper.delete(Wrappers.<WmNewsMaterial>lambdaQuery()
                    .eq(WmNewsMaterial::getNewsId, wmNews.getId()));
            updateById(wmNews);
        }

    }


}
